////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001.
// -------------------------------------------------------------------------
//  File name:   TerrainMoveTool.cpp
//  Version:     v1.00
//  Created:     11/1/2002 by Timur.
//  Compilers:   Visual C++ 6.0
//  Description: Terrain Modification Tool implementation.
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "TerrainPanel.h"
#include "TerrainMoveTool.h"
#include "TerrainMoveToolPanel.h"
#include "Viewport.h"
#include ".\Terrain\Heightmap.h"
#include "VegetationMap.h"
#include ".\Terrain\TerrainGrid.h"
#include "TrackView/TrackViewUndo.h"
#include "CryEditDoc.h"
#include "CustomMessageBox.h"
#include "ITransformManipulator.h"
#include "I3DEngine.h"
#include "GameEngine.h"
#include "AI/NavDataGeneration/Navigation.h"


namespace {
	CRect GetRectMargin(const CRect& rc, const CRect& rcCrop, int nRot)
	{
		CRect rcMar(rcCrop.left - rc.left, rcCrop.top - rc.top, rc.right - rcCrop.right, rc.bottom - rcCrop.bottom);
		if(nRot == 1)
			return CRect(rcMar.top, rcMar.right, rcMar.bottom, rcMar.left);
		if(nRot == 2)
			return CRect(rcMar.right, rcMar.bottom, rcMar.left, rcMar.top);
		if(nRot == 3)
			return CRect(rcMar.bottom, rcMar.left, rcMar.top, rcMar.right);
		return rcMar;
	}

	CRect CropRect(const CRect& rc, const CRect& rcMar)
	{
		return CRect(rc.left + rcMar.left, rc.top + rcMar.top, rc.right - rcMar.right, rc.bottom - rcMar.bottom);
	}
}



//////////////////////////////////////////////////////////////////////////
//class CUndoTerrainMoveTool

class CUndoTerrainMoveTool : public IUndoObject
{
public:
	CUndoTerrainMoveTool( CTerrainMoveTool *pMoveTool )
	{
		m_posSourceUndo = pMoveTool->m_source.pos;
		m_posTargetUndo = pMoveTool->m_target.pos;
	}
protected:
	virtual int GetSize() { return sizeof(*this); }
	virtual const char* GetDescription() { return ""; };

	virtual void Undo( bool bUndo )
	{
		CEditTool *pTool = GetIEditor()->GetEditTool();
		if (!pTool || !pTool->IsKindOf(RUNTIME_CLASS(CTerrainMoveTool)))
			return;
		CTerrainMoveTool *pMoveTool = (CTerrainMoveTool*)pTool;
		if (bUndo)
		{
			m_posSourceRedo = pMoveTool->m_source.pos;
			m_posTargetRedo = pMoveTool->m_target.pos;
		}
		pMoveTool->m_source.pos = m_posSourceUndo;
		pMoveTool->m_target.pos = m_posTargetUndo;
		if(pMoveTool->m_source.isSelected)
			pMoveTool->Select(1);
		if(pMoveTool->m_target.isSelected)
			pMoveTool->Select(2);
	}
	virtual void Redo()
	{
		CEditTool *pTool = GetIEditor()->GetEditTool();
		if (!pTool || !pTool->IsKindOf(RUNTIME_CLASS(CTerrainMoveTool)))
			return;
		CTerrainMoveTool *pMoveTool = (CTerrainMoveTool*)pTool;
		pMoveTool->m_source.pos = m_posSourceRedo;
		pMoveTool->m_target.pos = m_posTargetRedo;
		if(pMoveTool->m_source.isSelected)
			pMoveTool->Select(1);
		if(pMoveTool->m_target.isSelected)
			pMoveTool->Select(2);
	}
private:
	Vec3 m_posSourceRedo;
	Vec3 m_posTargetRedo;
	Vec3 m_posSourceUndo;
	Vec3 m_posTargetUndo;
};




//////////////////////////////////////////////////////////////////////////
//class CTerrainMoveTool


//////////////////////////////////////////////////////////////////////////
IMPLEMENT_DYNCREATE(CTerrainMoveTool,CEditTool)

Vec3 CTerrainMoveTool::m_dym(512,512,1024);
int CTerrainMoveTool::m_targetRot = 0.0f;
//SMTBox CTerrainMoveTool::m_source;
//SMTBox CTerrainMoveTool::m_target;

//////////////////////////////////////////////////////////////////////////
CTerrainMoveTool::CTerrainMoveTool() :
	m_archive(0),
	m_isSyncHeight(false),
	m_panelId(0),
	m_panel(0)
{
}

//////////////////////////////////////////////////////////////////////////
CTerrainMoveTool::~CTerrainMoveTool()
{
	if (m_archive)
		delete m_archive;
}

//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::BeginEditParams( IEditor *ie,int flags )
{
	m_ie = ie;

	CUndo undo( "Unselect All" );
	GetIEditor()->ClearSelection();

	if (!m_panelId)
	{
		m_panel = new CTerrainMoveToolPanel(this,AfxGetMainWnd());
		m_panelId = m_ie->AddRollUpPage( ROLLUP_TERRAIN,"Terrain Move Tool",m_panel );
		AfxGetMainWnd()->SetFocus();
	}

	if(m_source.isCreated)
		m_source.isShow = true;
	if(m_target.isCreated)
		m_target.isShow = true;

	//CPoint hmapSrcMin,hmapSrcMax;
	//hmapSrcMin = GetIEditor()->GetHeightmap()->WorldToHmap(srcBox.min);
	//hmapSrcMax = GetIEditor()->GetHeightmap()->WorldToHmap(srcBox.max);
	//m_srcRect.SetRect( hmapSrcMin,hmapSrcMax );
			
	//GetIEditor()->GetHeightmap()->ExportBlock( m_srcRect,*m_archive );
}

//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::EndEditParams()
{
	if (m_panelId)
	{
		m_ie->RemoveRollUpPage(ROLLUP_TERRAIN,m_panelId);
		m_panelId = 0;
		m_panel = 0;
	}
	Select(0);
}

//////////////////////////////////////////////////////////////////////////
bool CTerrainMoveTool::MouseCallback( CViewport* pView, EMouseEvent event, CPoint &point, int flags )
{
	if (event == eMouseMove)
	{
		if(m_source.isSelected && !m_source.isCreated || m_target.isSelected && !m_target.isCreated)
		{
			Vec3 pos = pView->SnapToGrid(pView->ViewToWorld( point ));
			if(pos==Vec3(0,0,0))
			{
				// collide with Z=0 plane
				Vec3 src, dir;
				pView->ViewToWorldRay(point, src, dir);
				if(fabs(dir.z)>0.00001f)
					pos = pView->SnapToGrid(src - (src.z / dir.z) * dir);
			}

			if(m_source.isSelected)
			{
				m_source.pos = pos;
				Select(1);
			}
			if(m_target.isSelected)
			{
				m_target.pos = pos;
				Select(2);
				if(m_isSyncHeight)
					m_target.pos.z = m_source.pos.z;
			}
		}
	}
	else if (event == eMouseLDown)
	{
		// Close tool.
		if(!m_source.isSelected && !m_target.isSelected)
			GetIEditor()->SetEditTool(0);
	}
	else if (event == eMouseLUp)
	{
		if(m_source.isSelected && !m_source.isCreated)
		{
			m_source.isCreated = true;
			m_panel->UpdateButtons();
		}
		else if(m_target.isSelected && !m_target.isCreated)
		{
			m_target.isCreated = true;
			m_panel->UpdateButtons();
		}
		else
			GetIEditor()->AcceptUndo( "Move Box" );
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::Display( DisplayContext &dc )
{
	if(m_source.isShow)
	{
		dc.SetColor( RGB(255,128,128),1 );
		if(m_source.isSelected)
			dc.SetColor(dc.GetSelectedColor());
		dc.DrawWireBox(m_source.pos - m_dym/2, m_source.pos + m_dym/2);
	}

	if(m_target.isShow)
	{
		dc.SetColor( RGB(128,128,255),1 );
		if(m_target.isSelected)
			dc.SetColor(dc.GetSelectedColor());

		Matrix34 tm;
		tm.SetIdentity();
		if(m_targetRot)
		{
			float a=g_PI/2 * m_targetRot;
			tm.SetRotationZ(a);
		}
		tm.SetTranslation(m_target.pos);
		dc.PushMatrix( tm );
		dc.DrawWireBox( -m_dym/2, m_dym/2);
		dc.PopMatrix();
	}

	/*
	BBox box;
	GetIEditor()->GetSelectedRegion(box);

	Vec3 p1 = GetIEditor()->GetHeightmap()->HmapToWorld( CPoint(m_srcRect.left,m_srcRect.top) );
	Vec3 p2 = GetIEditor()->GetHeightmap()->HmapToWorld( CPoint(m_srcRect.right,m_srcRect.bottom) );

	Vec3 ofs = m_pointerPos - p1;
	p1 += ofs;
	p2 += ofs;

	dc.SetColor( RGB(0,0,255) );
	dc.DrawTerrainRect( p1.x,p1.y,p2.x,p2.y,0.2f );
	*/
}

//////////////////////////////////////////////////////////////////////////
bool CTerrainMoveTool::OnKeyDown( CViewport *view,uint32 nChar,uint32 nRepCnt,uint32 nFlags )
{
	bool bProcessed = false;
	return bProcessed;
}


//////////////////////////////////////////////////////////////////////////
bool CTerrainMoveTool::OnKeyUp( CViewport *view,uint32 nChar,uint32 nRepCnt,uint32 nFlags )
{
	return false;
}


//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::Move(bool isCopy, bool bOnlyVegetation, bool bOnlyTerrain)
{
	enum{
		eInit = 0,
		eCancel = 1,
		eIgnore = 2,
		eSave = 3,
		eHold = 4,
	};

	int nKey = CCustomMessageBox::Show(_T("Can't store complete undo for this operation. If Ignore is chosen, only the object movement will be undoable."), _T("Terrain Move Area"), _T("Cancel"), _T("Ignore"), _T("Save"), _T("Hold") );
	if(nKey==eCancel || nKey==eInit)
	{
		return;
	}
	else if(nKey==eSave)
	{
		GetIEditor()->GetDocument()->Save();
	}
	else if(nKey==eHold)
	{
		GetIEditor()->GetDocument()->HoldToFile( HOLD_FETCH_FILE );
	}
	else if(nKey==eIgnore)
	{
		// Just do moving without holding or saving
	}

	// Move terrain area.
	CUndo undo( "Copy Area" );
	CWaitCursor wait;

	CHeightmap *pHeightmap = GetIEditor()->GetHeightmap();
	if(!pHeightmap || !pHeightmap->GetHeight() || !pHeightmap->GetWidth())
		return;

	AABB srcBox(m_source.pos-m_dym/2, m_source.pos+m_dym/2);

	LONG bx1 = pHeightmap->WorldToHmap(srcBox.min).x;
	LONG by1 = pHeightmap->WorldToHmap(srcBox.min).y;
	LONG bx2 = pHeightmap->WorldToHmap(srcBox.max).x;
	LONG by2 = pHeightmap->WorldToHmap(srcBox.max).y;

	LONG xc = (bx1+bx2)/2;
	LONG yc = (by1+by2)/2;

	LONG cx1 = bx1;
	LONG cy1 = by1;

	CPoint hmapPosStart = pHeightmap->WorldToHmap(m_target.pos-m_dym/2) - CPoint(cx1,cy1);

	if(m_targetRot==1 || m_targetRot==3)
	{
		cx1 = xc - (by2-by1)/2;
		cy1 = yc - (bx2-bx1)/2;
		hmapPosStart = pHeightmap->WorldToHmap(m_target.pos-Vec3(m_dym.y/2, m_dym.x/2, m_dym.z)) - CPoint(cx1,cy1);
	}

#ifdef WIN64
	LONG blocksize = 4096;
#else 
	LONG blocksize = 512;
#endif

	for(LONG by = by1; by<by2; by+=blocksize)
	for(LONG bx = bx1; bx<bx2; bx+=blocksize)
	{
		LONG cx = bx;
		LONG cy = by;

		if(m_targetRot==1)
		{
			cx = xc - (yc - by);
			cy = yc + (xc - bx) - blocksize;
			if(cy < cy1) cy = cy1;
		}
		else if(m_targetRot==3)
		{
			cx = xc + (yc - by) - blocksize;
			cy = yc - (xc - bx);
			if(cx < cx1) cx = cx1;
		}
		else if(m_targetRot==2)
		{
			cx = bx2-1 + bx1 - bx - blocksize;
			if(cx < bx1) cx = bx1;

			cy = by2-1 + by1 - by - blocksize;
			if(cy < by1) cy = by1;
		}

		// Move terrain heightmap block.

		CRect rcHeightmap(0, 0, pHeightmap->GetWidth(), pHeightmap->GetHeight());

		// left-top position of destination rectangle in Heightmap space
		CPoint posDst = hmapPosStart + CPoint(cx, cy);

		// source rectangle in Heightmap space
		CRect rcSrc(bx, by, min(bx + blocksize+1, bx2), min(by + blocksize+1, by2));
		// destination rectangle in Heightmap space
		CRect rcDst(posDst.x, posDst.y, posDst.x+rcSrc.Width(), posDst.y+rcSrc.Height());
		if(m_targetRot==1 || m_targetRot==3)
		{
			rcDst.right = posDst.x+rcSrc.Height();
			rcDst.bottom = posDst.y+rcSrc.Width();
		}

		// Crop destination region by terrain outside 
		CRect rcCropDst( rcDst & rcHeightmap);
		CRect rcMar = GetRectMargin(rcDst, rcCropDst, 4 - m_targetRot);
		CRect rcCropSrc = CropRect(rcSrc, rcMar); // sync src with dst

		if(!rcCropSrc.IsRectEmpty())
		{
			if (m_archive)
				delete m_archive;
			m_archive = new CXmlArchive("Root");

			// don't need crop by source outside for Heightmap moving (only destination crop)
			// data (height values) from source outside will be filled by 0
			m_archive->bLoading = false;
			pHeightmap->ExportBlock( rcCropSrc, *m_archive, isCopy && (bOnlyVegetation || (!bOnlyVegetation && !bOnlyTerrain)));

			m_archive->bLoading = true;
			pHeightmap->ImportBlock( *m_archive, CPoint(rcCropDst.left, rcCropDst.top), true, (m_target.pos-m_source.pos).z, bOnlyVegetation && !bOnlyTerrain, m_targetRot);
		}

		// Crop source region by terrain outside 
		rcCropSrc &= rcHeightmap;
		rcMar = GetRectMargin(rcSrc, rcCropSrc, m_targetRot);
		rcCropDst = CropRect(rcDst, rcMar); // sync dst with src

		if(!rcCropSrc.IsRectEmpty() && (!bOnlyVegetation || bOnlyTerrain))
		{
			CRGBLayer * pLayer = &pHeightmap->m_TerrainRGBTexture;
			uint32 nRes = pLayer->CalcMaxLocalResolution((float)rcCropSrc.left/pHeightmap->GetWidth(), (float)rcCropSrc.top/pHeightmap->GetHeight(), (float)rcCropSrc.right/pHeightmap->GetWidth(), (float)rcCropSrc.bottom/pHeightmap->GetHeight());
			float fTerrainSize = (float)GetIEditor()->Get3DEngine()->GetTerrainSize();

			{
				CImage image;
				CImage tmpImage;
				image.Allocate((rcCropSrc.Width())*nRes/pHeightmap->GetWidth(), (rcCropSrc.Height())*nRes/pHeightmap->GetHeight());
				pLayer->GetSubImageStretched(
					(float)rcCropSrc.left/pHeightmap->GetWidth(), (float)rcCropSrc.top/pHeightmap->GetHeight(),
					(float)rcCropSrc.right/pHeightmap->GetWidth(), (float)rcCropSrc.bottom/pHeightmap->GetHeight(), image);

				if(m_targetRot)
					tmpImage.RotateOrt(image, m_targetRot);

				pLayer->SetSubImageStretched(
					((float)rcCropDst.left)/pHeightmap->GetWidth(), ((float)rcCropDst.top)/pHeightmap->GetHeight(), 
					((float)rcCropDst.right)/pHeightmap->GetWidth(), ((float)rcCropDst.bottom)/pHeightmap->GetHeight(), m_targetRot ? tmpImage : image);
			}

			for(int x=0; x<=rcCropSrc.Width(); x++)
				for(int y=0; y<=rcCropSrc.Height(); y++)
				{
					uint32 lid = pHeightmap->GetLayerIdAt(x+rcCropSrc.left, y+rcCropSrc.top);
					if(m_targetRot==1)
						pHeightmap->SetLayerIdAt(rcCropDst.right-y, x, lid);
					else if(m_targetRot==2)
						pHeightmap->SetLayerIdAt(rcCropDst.right-x, rcCropDst.bottom-y, lid);
					else if(m_targetRot==3)
						pHeightmap->SetLayerIdAt(y, rcCropDst.bottom-x, lid);
					else
						pHeightmap->SetLayerIdAt(x+rcCropDst.left, y+rcCropDst.top, lid);
				}
			
			pHeightmap->UpdateLayerTexture(rcCropDst);
		}
		if (m_archive)
		{
			delete m_archive;
			m_archive = 0;
		}
	}

	GetIEditor()->GetObjectManager()->SendEvent(EVENT_CLEAR_AIGRAPH);
	GetIEditor()->GetGameEngine()->GetNavigation()->FlushSystemNavigation();

	if (IAISystem *ai = GetIEditor()->GetSystem()->GetAISystem())
		ai->FlushSystemNavigation();


	if(!isCopy && (bOnlyVegetation || (!bOnlyVegetation && !bOnlyTerrain)))
	{
		pHeightmap->GetVegetationMap()->RepositionArea(srcBox, m_target.pos-m_source.pos, m_targetRot);
	}

	if(!isCopy && (!bOnlyVegetation && !bOnlyTerrain))
	{
		GetIEditor()->GetObjectManager()->MoveObjects( srcBox, m_target.pos-m_source.pos, m_targetRot, isCopy);
		OffsetTrackViewPositionKeys(m_target.pos-m_source.pos);
	}

	/*
	// Load selection from archive.
	XmlNodeRef objRoot = m_archive->root->findChild("Objects");
	if (objRoot)
	{
		GetIEditor()->ClearSelection();
		CObjectArchive ar( GetIEditor()->GetObjectManager(),objRoot,true );
		GetIEditor()->GetObjectManager()->LoadObjects( ar,false );
	}
	*/
	GetIEditor()->ClearSelection();
}

//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::SetArchive( CXmlArchive *ar )
{
	if (m_archive)
		delete m_archive;
	m_archive = ar;

	int x1,y1,x2,y2;
	// Load rect size our of archive.
	m_archive->root->getAttr( "X1",x1 );
	m_archive->root->getAttr( "Y1",y1 );
	m_archive->root->getAttr( "X2",x2 );
	m_archive->root->getAttr( "Y2",y2 );

	m_srcRect.SetRect( x1,y1,x2,y2 );
}

//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::Select(int nBox)
{
	m_source.isSelected = false;
	m_target.isSelected = false;

	if(nBox==0)
	{
		GetIEditor()->ShowTransformManipulator(false);
		m_source.isShow = false;
		m_target.isShow = false;
	}

	if(nBox==1)
	{
		m_source.isSelected = true;
		m_source.isShow = true;
		ITransformManipulator *pManipulator = GetIEditor()->ShowTransformManipulator(true);
		Matrix34 tm;
		tm.SetIdentity();
		tm.SetTranslation( m_source.pos );
		pManipulator->SetTransformation( COORDS_LOCAL, tm );
	}

	if(nBox==2)
	{
		m_target.isSelected = true;
		m_target.isShow = true;
		ITransformManipulator *pManipulator = GetIEditor()->ShowTransformManipulator(true);
		Matrix34 tm;
		tm.SetIdentity();
		if(m_targetRot)
		{
			float a=g_PI/2 * m_targetRot;
			tm.SetRotationZ(a);
		}
		tm.SetTranslation( m_target.pos );
		pManipulator->SetTransformation( COORDS_LOCAL, tm );
	}
}

//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::OnManipulatorDrag( CViewport *view,ITransformManipulator *pManipulator,CPoint &p0,CPoint &p1,const Vec3 &value )
{
	int editMode = GetIEditor()->GetEditMode();
	if (editMode == eEditModeMove)
	{
		CHeightmap *pHeightmap = GetIEditor()->GetHeightmap();
		GetIEditor()->RestoreUndo();

		Vec3 pos = m_source.pos;
		Vec3 val = value;

		Vec3 max = pHeightmap->HmapToWorld(CPoint(pHeightmap->GetWidth(), pHeightmap->GetHeight()));

		uint32 wid = max.x;
		uint32 hey = max.y;
		
		if(m_target.isSelected)
			pos = m_target.pos;

		pManipulator = GetIEditor()->ShowTransformManipulator(true);
		Matrix34 tm = pManipulator->GetTransformation(COORDS_LOCAL);

		if (CUndo::IsRecording())
			CUndo::Record( new CUndoTerrainMoveTool(this) );

		if(m_source.isSelected)
		{
			m_source.pos+=val;
			if(m_isSyncHeight)
				m_target.pos.z = m_source.pos.z;
		}
		
		if(m_target.isSelected)
		{
			m_target.pos+=val;
			if(m_isSyncHeight)
				m_source.pos.z = m_target.pos.z;
		}

		tm.SetTranslation( pos + val );
		pManipulator->SetTransformation( COORDS_LOCAL,tm );
	}
}


//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::SetDym(Vec3 dym)
{	
	m_dym = dym;
}


//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::SetTargetRot(int targetRot)
{	
	m_targetRot = targetRot;
}


//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::OffsetTrackViewPositionKeys(const Vec3& offset)
{
	for(int k=0; k<GetIEditor()->GetMovieSystem()->GetNumSequences(); ++k)
	{
		IAnimSequence *seq = GetIEditor()->GetMovieSystem()->GetSequence(k);
		for (int i = 0,num = seq->GetNodeCount(); i < num; i++)
		{
			IAnimNode *pAnimNode = seq->GetNode(i);

			if (pAnimNode == NULL)
				continue;

			EAnimNodeType nodeType = pAnimNode->GetType();
			bool bEntityNode = nodeType == ANODE_ENTITY || nodeType == ANODE_CAMERA;
			bool bAlreadyMovedByNodeOwner = pAnimNode->GetNodeOwner() != NULL;
			if (bEntityNode && !bAlreadyMovedByNodeOwner)
			{
				bool bEntityWithoutParent = true;
				if (pAnimNode->GetEntity() && pAnimNode->GetEntity()->GetParent())
					bEntityWithoutParent = false;
				if (bEntityWithoutParent)
				{
					IAnimTrack *pTrack = pAnimNode->GetTrackForParameter(APARAM_POS);
					if (pTrack)
					{
						CUndo::Record(new CUndoTrackObject(pTrack, pAnimNode->GetSequence()));
						pTrack->OffsetKeyPosition(offset);
					}
				}
			}
		}
	}
}


//////////////////////////////////////////////////////////////////////////
void CTerrainMoveTool::SetSyncHeight(bool isSyncHeight)
{
	m_isSyncHeight = isSyncHeight;
	if(m_isSyncHeight)
	{
		m_target.pos.z = m_source.pos.z;
		if(m_target.isSelected)
			Select(2);
	}
}
